#include "os_shmem.h"
#include "hash.h"

static hash_t shmem_hash;
static bool hash_init = FALSE;

// Init device
err_t init_shmem(void)
{
    err_t hash_err;

    if (hash_init) {
        return ERR_SUCCESS;
    }
    
    hash_err = create_hash(&shmem_hash, 128, HASH_TYPE_STRING);
    if (hash_err != ERR_SUCCESS) {
        printf("shmem hash creation failed: 0x%x\n", hash_err);
	return ERR_INTERNAL_ERROR;
    }

    hash_init = TRUE;
    return ERR_SUCCESS;
}

static inline
err_t validate_device_params(dev_info_t *device_info)
{

    if (!device_info) {
        return ERR_INVALID;
    }

    if (strlen(device_info->name) == 0) {
        return ERR_INVALID;
    }

    if (device_info->size > SHMEM_MAX_SIZE) {
        return ERR_OVERFLOW;
    }

    return ERR_SUCCESS;
 }


err_t lookup_shmem(dev_info_t *device_info, device_t **device)
{
    hash_t *hash_ptr = &shmem_hash;
    device_t *dev = NULL;
    err_t dev_error;

    if (!device_info || !device) {
        return ERR_INVALID;
    }

    dev_error = validate_device_params(device_info);
    if (dev_error != ERR_SUCCESS) {
        return dev_error;
    }

    lookup_hash(hash_ptr, device_info->name, (uint32_t *)&dev);
    printf("SHMEM lookup result for %s: 0x%x\n", device_info->name, (uint32_t)dev);
    *device = dev;

    return ERR_SUCCESS;
}



err_t open_shmem(dev_handle_t *handle, device_t **device)
{
    hash_t *hash_ptr = &shmem_hash; 
    device_t *dev = NULL;
    dev_open_mode_t open_mode;
    uint8_t *name = NULL;
    uint8_t *memory = NULL;
    err_t dev_error;

    if (!handle || !device) {
        return ERR_INVALID;
    }

    dev_error = validate_device_params(&handle->dev_info);
    if (dev_error != ERR_SUCCESS) {
        return dev_error;
    }

    open_mode = handle->open_mode;

    /*
    if (dev_handle->open_mode != DEVICE_MODE_CREATE_ALWAYS) {
        return ERR_INVALID_MODE;
    }
    */

    // lookup whether the specified shmem block already exists
    lookup_hash(hash_ptr, handle->dev_info.name, (uint32_t *)&dev);
    if (dev) {
        // SHMEM is already open
	if (open_mode == DEVICE_MODE_CREATE_ALWAYS) {
	    return ERR_ALREADY_EXIST;
	} else {
	   
	    dev->ref_count++;
	    if (handle->access_info.permissions & DEVICE_MODE_WRITE) {
	        dev->num_writers++;
	    }

	    handle->device_created = FALSE;
	    *device = dev;
	    return ERR_SUCCESS;
	}
    } else {

        if (open_mode == DEVICE_MODE_OPEN_EXISTING) {
	    return ERR_NOT_FOUND;
	}

        // Create device
        dev = (device_t *)calloc(1, sizeof(device_t));
        if (!dev) {
            return ERR_NO_MEM;
        }

	name = (uint8_t *)calloc(1, strlen(handle->dev_info.name)+1);
	if (!name) {
	    free(dev);
	    return ERR_NO_MEM;
	}

	memory = calloc(1, handle->dev_info.size);
	if (!memory) {
	    free(dev);
	    free(name);
	    return ERR_NO_MEM;
	}
    
        dev->device_info = handle->dev_info;
        dev->device_info.name = name;   // names not required for sockets
        dev->mode =  0; // ??
        dev->state = DEVICE_STATE_OPENED_READ_WRITE;
        dev->ref_count = 1;
	dev->device_info.address = (uint32_t)memory;
	if (handle->access_info.permissions & DEVICE_MODE_WRITE) {
	    dev->num_writers++;
	}

	if (open_mode == DEVICE_MODE_CREATE_IF_NOT_FOUND) {
	    // Process wants to re-use the shared memory 
	    // across restarts. We keep ref_count high to 
	    // ensure that the memory device is not deleted
	    dev->ref_count++;
	}

        if (insert_hash(hash_ptr, handle->dev_info.name, (uint32_t)dev) != ERR_SUCCESS) {
            printf("Failed inserting shmem %s into hash\n", handle->dev_info.name);
	    free(dev);
	    free(name);
	    free(memory);
	    return ERR_INTERNAL_ERROR;
	}

    }

    handle->device_created = TRUE;
    *device = dev;

    return ERR_SUCCESS;
}


err_t close_shmem(dev_access_t *access_info)
{
    device_t *dev = NULL;
    device_t *stored_dev = NULL;
    err_t dev_error;
    hash_t *hash_ptr = &shmem_hash;
    err_t hash_err;

    if (!access_info || !access_info->dev_ptr) {
        return ERR_INVALID;
    }

    dev = (device_t *)access_info->dev_ptr;
    dev_error = lookup_shmem(&dev->device_info, &stored_dev);
    if (dev_error != ERR_SUCCESS || dev != stored_dev) {
        return ERR_INVALID;
    }

    if (access_info->permissions & DEVICE_MODE_WRITE) {
        dev->num_writers--;
    }

    dev->ref_count--;
    if (dev->ref_count) {
        // Other processes are still using the shared memory
	// So, do not physically remove the device
	return ERR_SUCCESS;
    }


    hash_err = remove_hash_node(hash_ptr, &dev->device_info.name);
    if (hash_err != ERR_SUCCESS) {
        return ERR_INTERNAL_ERROR;
    }

    free((void *)dev->device_info.name);
    free((void *)dev->device_info.address);
    free(dev);
    
    return ERR_SUCCESS;
}
